package com.zmkj.cn.comm.fileter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zmkj.cn.comm.baseenum.IndexEnum;
import com.zmkj.cn.comm.constant.RedisConstant;
import com.zmkj.cn.comm.constant.ResultCode;
import com.zmkj.cn.comm.exception.BaseException;
import com.zmkj.cn.comm.util.CoyUtils;
import com.zmkj.cn.comm.util.IpUtils;
import com.zmkj.cn.comm.vo.CommonResultVo;
import com.zmkj.cn.comm.vo.IErrorCode;
import com.zmkj.cn.sys.constant.CommConstant;
import com.zmkj.cn.sys.constant.LoginPortConstant;
import com.zmkj.cn.sys.constant.WechatConstant;
import com.zmkj.cn.sys.service.ISysLoginService;
import com.zmkj.cn.sys.util.RedisUtil;
import com.zmkj.cn.sys.vo.input.SysLoginWechatInputVo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;


/**
 * @author : 伍绍宇 Coy
 * @date : 2021-04-20 13:53
 **/
@WebFilter(filterName = "AllFilter", urlPatterns = "/*")
@Component
@Slf4j
class AllFilter extends OncePerRequestFilter {

    @Resource
    private RestTemplate restTemplate;
    @Resource
    private RedisUtil redisUtil;

    @Resource
    ISysLoginService loginService;

    @Value("${center.admin.access}")
    private String adminAccess;
    @Value("${center.admin.secret}")
    private String adminSecret;

    @SneakyThrows
    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) {
        LogInfo logInfo = preRequest(request);
        try {
            // 获取URL
            String url = request.getServletPath();
            // 管理员权限验证
            if (adminValid(request, url, response, filterChain)) {
                return;
            }
            // 用户注册权限
            if (userMakeValid(request, url, response, filterChain)) {
                return;
            }
            List<String> notLogin = List.of("firstLoginWeChat", "bindPhone", "/send", "upload");



            // 登录和退出登录放行
            if (stringContainsList(notLogin, url)) {
                filterChain.doFilter(request, response);
                return;
            }
            // 过滤未传递token数据
            String token = request.getHeader(WechatConstant.TOKEN.value);

            if (token == null) {

                if(("/outer/getEditionList".equals(url) || "/busOrderFault/scan".equals(url)) && request.getParameter("hospitalId") != null){

                    log.info("token is null url:{},hospital:{}",url,request.getParameter("hospitalId"));
                    Object map = redisUtil.getMap(RedisConstant.HOSPITAL_INFO.value, request.getParameter("hospitalId"));
                    if (map == null) {
                        // 找不到对应医院
                        throwException(response, ResultCode.HOSPITAL_ERROR,logInfo);
                        return;
                    }
                    String hospitalInfo = map.toString();
                    String forwardUrl = hospitalInfo + url;
                    proxy(request, response, "", forwardUrl,false,logInfo);

                }

                // 新用户微信保修
                if("/busOrderFault/createOrderByWechat".equals(url)){
                    log.info("token is null url:{},hospital:{},deptcode:{},phone:{}",url,request.getParameter("hospitalId"),request.getParameter("deptCode"),request.getParameter("phone"));
                    if(request.getParameter("hospitalId") != null && request.getParameter("deptCode") != null && request.getParameter("phone") != null){

                        Object map = redisUtil.getMap(RedisConstant.HOSPITAL_INFO.value, request.getParameter("hospitalId"));
                        if (map == null) {
                            // 找不到对应医院
                            throwException(response, ResultCode.HOSPITAL_NOT_FOUND,logInfo);
                            return;
                        }
                        String hospitalInfo = map.toString();
                        String forwardUrl = hospitalInfo + url;
                        proxy(request, response, "", forwardUrl,true,logInfo);

                    }
                }
                throwException(response, ResultCode.NOT_HAVE_TOKEN,logInfo);
                return;
            }
            Object hospitalKey = redisUtil.get(token);
            if (hospitalKey == null) {
                // token失效
                throwException(response, ResultCode.UNAUTHORIZED,logInfo);
                return;
            }
            Object map = redisUtil.getMap(RedisConstant.HOSPITAL_INFO.value, hospitalKey.toString());
            if (map == null) {
                // 找不到对应医院
                throwException(response, ResultCode.HOSPITAL_NOT_FOUND,logInfo);
                return;
            }
            String hospitalInfo = map.toString();
            String forwardUrl = hospitalInfo + url;
            proxy(request, response, token, forwardUrl,false,logInfo);
        }catch (Exception e){
            logInfo.setException(e);
            logInfo.setResult(false);
            throw e;
        }
        finally {
            logInfo.log();
        }
    }


    private void proxy(HttpServletRequest request, HttpServletResponse response, String token, String forwardUrl,Boolean needLogin,LogInfo info) throws Exception {
        try {
            HttpHeaders headers = new HttpHeaders();
            MediaType type = MediaType.parseMediaType(request.getContentType());
            headers.setContentType(type);
            headers.add(WechatConstant.TOKEN.value, request.getHeader(WechatConstant.TOKEN.value));
            headers.add(WechatConstant.LOGIN_PORT.value, LoginPortConstant.WECHAT.value + "");
            HttpEntity<MultiValueMap<String, String>> formEntity = new HttpEntity<>(getParameter(request), headers);

            ResponseEntity<byte[]> proxyResponse = restTemplate.postForEntity(forwardUrl, formEntity, byte[].class);
//            String forObject = restTemplate.postForObject(forwardUrl, formEntity, String.class);
            // 检查内容类型
            MediaType responseType = proxyResponse.getHeaders().getContentType();
            if (responseType != null) {
                if (responseType.includes(MediaType.APPLICATION_JSON) || responseType.includes(MediaType.TEXT_PLAIN)) {
                    String responseBody = new String(proxyResponse.getBody(), StandardCharsets.UTF_8);
                    // 处理字符串响应
                    if (StringUtils.isNotEmpty(responseBody)) {
                        //将返回值写入loginfo中
                        info.setReturnResult(responseBody);
                        JSONObject jsonObject = JSONObject.parseObject(responseBody);
                        Object code = jsonObject.get(WechatConstant.CODE.value);
                        if (code != null && ResultCode.UNAUTHORIZED.code == Integer.parseInt(code.toString())) {
                            log.info("invailed token:{}", token);
                            redisUtil.deleteKey(token);
                        }
                    }
                }
            }

            if(needLogin){
                SysLoginWechatInputVo loginVo = new SysLoginWechatInputVo();
                loginVo.setCode(request.getParameter("code"));
                loginVo.setPhone(request.getParameter("phone"));
                loginVo.setNewUserLogin(true);
                loginService.firstLoginWeChat(request, loginVo);
            }
            // 返回
            preHandle(response,proxyResponse.getHeaders(), proxyResponse.getBody());
        }catch (Exception e){
            //代理出现异常
            info.setResult(false);
            info.setException(e);
            if(e instanceof HttpClientErrorException){
                if(((HttpClientErrorException) e).getRawStatusCode()==404){
                    response.sendError(((HttpClientErrorException) e).getRawStatusCode());
                    return;
                }
            }
            throw e;
        }
    }

    /**
     * 管理员验证资源
     *
     * @author 伍绍宇
     * @date 2021/04/28
     */
    private boolean adminValid(HttpServletRequest request, String url, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        List<String> validList = List.of("registerHospital");
        if (stringContainsList(validList, url)) {
            String accessKey = request.getHeader(CommConstant.ACCESSKEY);
            String secretKey = request.getHeader(CommConstant.SECRETKEY);
            if (StringUtils.isEmpty(accessKey) || StringUtils.isEmpty(secretKey)) {
                throw new BaseException("请联系管理员获取通行证");
            }
            if (adminAccess.equals(accessKey) && adminSecret.equals(secretKey)) {
                filterChain.doFilter(request, response);
                return true;
            }
            throw new BaseException("通行证异常！已经记录该IP号");
        }
        return false;
    }

    /**
     * 用户操作
     *
     * @author 伍绍宇
     * @date 2021/04/28
     */
    private boolean userMakeValid(HttpServletRequest request, String url, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        List<String> validList = List.of("registerUser", "removeUser","/user/readUser");
        if (stringContainsList(validList, url)) {
            String accessKey = request.getHeader(CommConstant.ACCESSKEY);
            String secretKey = request.getHeader(CommConstant.SECRETKEY);
            if (StringUtils.isEmpty(accessKey) || StringUtils.isEmpty(secretKey)) {
                throw new BaseException("请联系管理员获取通行证");
            }

            if (redisUtil.hashKey(RedisConstant.ACCESS_SECRET.value, accessKey) && redisUtil.getMap(RedisConstant.ACCESS_SECRET.value, accessKey).equals(secretKey)) {
                filterChain.doFilter(request, response);
                return true;
            }
            throw new BaseException("通行证异常！已经记录该IP号");
        }
        return false;
    }


    /**
     * 写出
     *
     * @author 伍绍宇 Coy
     * @date 2021年4月22日 18:28:07
     */
    public void preHandle(HttpServletResponse response,HttpHeaders headers, byte[] body) throws Exception {
        //重置response
        //response.reset();
        //设置编码格式
        response.setCharacterEncoding("UTF-8");
        if(ObjectUtils.isEmpty(headers)){
            response.setContentType("application/json;charset=UTF-8");
        }else{
            response.setHeader(HttpHeaders.CONTENT_TYPE,headers.getFirst(HttpHeaders.CONTENT_TYPE));
//            for (String key : headers.keySet()) {
//                response.setHeader(key,headers.getFirst(key));
//            }
        }
        response.getOutputStream().write(body);
        response.getOutputStream().flush();
//        response.setHeader();
//        PrintWriter pw = response.getWriter();
//        pw.write(body);
//        pw.flush();
//        pw.close();
    }

    /**
     * 封装抛出异常
     *
     * @author 伍绍宇 Coy
     * @date 2021年4月22日 18:27:55
     */
    private void throwException(HttpServletResponse response, IErrorCode msg,LogInfo info) throws Exception {
        preHandle(response, null, JSON.toJSONString(CommonResultVo.failed(msg)).getBytes("utf-8"));
        //记录异常
        info.setResult(false);
        info.setException(new BaseException(msg.getMsg()));
    }

    /**
     * 获取body参数
     *
     * @author 伍绍宇
     * @date 2021/04/21
     */
    protected MultiValueMap<String, String> getParameter(HttpServletRequest request) {
        MultiValueMap<String, String> maps = new LinkedMultiValueMap<>();
        Map<String, String[]> datas = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : datas.entrySet()) {
            String[] value = entry.getValue();
            if (value.length > IndexEnum.ONE.code) {
                CoyUtils.getFieldStrByList(value);
            } else {
                maps.add(entry.getKey(), value[IndexEnum.ZERO.code]);
            }
        }
        return maps;
    }

    /**
     * 判断url是否以集合参数开头
     *
     * @param notLoginList 集合参数
     * @param url          地址
     */
    private boolean stringContainsList(List<String> notLoginList, String url) {
        for (String notLogin : notLoginList) {
            if (url.contains(notLogin)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 预装载分析请求实体组装
     * @param request
     */
    private LogInfo preRequest(HttpServletRequest request) {
        LogInfo logInfo = new LogInfo(request);
        if(!StringUtils.isEmpty(logInfo.getToken())){
            //如果用户存在token 检查是否匹配医院
            Object hospitalId = redisUtil.get(logInfo.getToken());
            if (!ObjectUtils.isEmpty(hospitalId)) {
                //判断是否能够根据找到医院信息
                logInfo.setHospitalId((String)hospitalId);
                Object hospitalInfo = redisUtil.getMap(RedisConstant.HOSPITAL_DETAIL.value,logInfo.getHospitalId());
                if(!ObjectUtils.isEmpty(hospitalInfo)){
                    //根据医院ID找到了医院详情信息
                    JSONObject hospitalInfoJson = JSONObject.parseObject(hospitalInfo.toString());
                    logInfo.setHospitalName(hospitalInfoJson.getString("serverName"));
                    logInfo.setHospitalApi(hospitalInfoJson.getString("serverUrl"));
                }
            }

        }
        return logInfo;
    }

    @Data
    @NoArgsConstructor
    private class LogInfo{
        /**
         * 医院名称
         */
        private String hospitalName;
        /**
         * 请求地址
         */
        private String url;
        /**
         * 执行结果 (1正常，2异常)
         */
        private boolean result;
        /**
         * 转发返回结果(医院返回内容)
         */
        private String returnResult;
        /**
         * 请求Ip
         */
        private String ip;
        /**
         * 用户token
         */
        private String token;

        /**
         * 请求方式
         */
        private String method;
        /**
         * 客户端类型
         */
        private String userAgent;
        /**
         * 医院标识
         */
        private String hospitalId;

        /**
         * 医院api地址
         */
        private String hospitalApi;

        /**
         * 请求参数
         */
        private Object requestParam;

        private Exception exception;

        public LogInfo(HttpServletRequest request) {
            setUrl(request.getServletPath());
            setToken(request.getHeader(WechatConstant.TOKEN.value));
            setMethod(request.getMethod());
            setUserAgent(request.getHeader("user-agent"));
            setIp(IpUtils.getIpAddress(request));
            setRequestParam(JSON.toJSONString(request.getParameterMap()));
            setResult(true);
        }

        public void setReturnResult(String returnResult) {
            this.returnResult = returnResult;
            try {
                JSONObject result = JSONObject.parseObject(returnResult);
                setResult(result.getInteger("code")==0);
            }catch (Exception e){
                log.error("解析请求分析日志出现异常{}",e);
            }

        }

        public void log(){
            if(isResult()){
                log.info("@shenjun:分发成功:{}\n",this);
            }else{
                log.error("@shenjun:分发异常:{}\n",this);
            }
        }
    }


}
